package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"git.sr.ht/~mil/mobroute/src/apidb"
	"git.sr.ht/~mil/mobroute/src/apiroute"
	"git.sr.ht/~mil/mobroute/src/apistops"
	"git.sr.ht/~mil/mobroute/src/logger"
	"git.sr.ht/~mil/mobroute/src/utilfuncs"
	"gopkg.in/yaml.v3"
	"log"
	"os"
	"runtime/pprof"
	"strings"
)

func main() {
	var (
		jsonBytes               []byte
		commandData             any
		err                     error
		flagBaseDebugCategories string
		flagBaseDebugProfile    string
		flagBaseOutputFormat    string

		commandDefaultsRoute = "{filter: {mdbid: [516]}, from: {lat: 40.70940, lon: -74.00537}, to: {lat: 40.72879, lon: -73.95215}, output_formats: [route_legs, diagnostics_route, diagnostics_perf, geojson, mapurl]}"
		commandDefaultsStops = "{filter: {mdbid: [516]}}"
		commandDefaultsDb    = "{mdbid: [516]}"

		flagDbSet    = flag.NewFlagSet("db", flag.ExitOnError)
		flagDbParams = flagDbSet.String("f", commandDefaultsDb, "")

		flagRouteSet    = flag.NewFlagSet("route", flag.ExitOnError)
		flagRouteParams = flagRouteSet.String(
			"p",
			commandDefaultsRoute,
			`YAML Parameters for db subcommand; note JSON is also valid YAML as well

Valid properties:
  - filter: Object based on mobsql's filter specification, see git.sr.ht/~mil/mobsql (default: {mdbid: [516]})
  - from: Object with two float properties, lat & lon (default: {lat: 40.70940, lon: -74.00537})
  - to: Object with two float properties, lat & lon (default: {lat: 40.72879, lon: -73.95215})
  - time: Datetime to depart from trip, if unset defaults to current time / now (default: null)
  - max_walk_seconds: Maximum seconds walking distance on start/end of trip to get to/from origin/destination stops (default: 1200)
  - max_trip_seconds: Maximum seconds the entire route/trip can take (default: 14400)
  - min_transfer_seconds: Minimum seconds each transfer in the route can take (default: 180)
  - max_transfer_seconds: Maximum seconds each transfer in the route can take (default: 2400)
  - walkspeed_km_hr: Ratio of kilometers per hour the user walk (default: 4.5)
  - transfers_table: Whether to use source GTFS transfers.txt (default: true)
  - transfers_generated: Whether to generate transfers, infered if >1 source (default: false)
  - debug_formatting: Whether to output verbose debug route formatting (default: false)
  - cache_dir: Directory to store sqlite db, defaults to ~/.cache/mobroute if null (default: null)
  - output_formats: Array of formats for the output (default: [legs, diagnostics_route, diagnostics_perf])
	`)

		flagStopsSet    = flag.NewFlagSet("stops", flag.ExitOnError)
		flagStopsParams = flagStopsSet.String("p", commandDefaultsStops, "YAML Parameters for stops command")
	)

	// Same usage for all subcommands
	for _, f := range []*flag.FlagSet{flagRouteSet, flagStopsSet} {
		f.StringVar(
			&flagBaseDebugCategories, "d", "i",
			"Verbose debug categories: (i) Information (w) Warn (d) Debug",
		)
		f.StringVar(
			&flagBaseDebugProfile, "dprof", "",
			"Write debug pprof CPU profile to file",
		)
		f.StringVar(
			&flagBaseOutputFormat, "format", "json",
			"Output format, currently only json supported",
		)
	}

	validSubcommands := []string{"route", "stops", "version", "dbload", "dbcompute", "dbstatus"}

	if len(os.Args) < 2 || !utilfuncs.StringInArray(validSubcommands, os.Args[1]) {
		fmt.Println("Provide valid subcommand as first arg, valid subcommands:", validSubcommands)
		os.Exit(1)
	}

	if os.Args[1] == "route" {
		flagRouteSet.Parse(os.Args[2:])
		globalDeferedFn := globalsSetup(flagBaseDebugCategories, flagBaseDebugProfile)
		defer globalDeferedFn()
		commandData, err = cmdRoute(*flagRouteParams)
	} else if os.Args[1] == "stops" {
		flagStopsSet.Parse(os.Args[2:])
		globalDeferedFn := globalsSetup(flagBaseDebugCategories, flagBaseDebugProfile)
		defer globalDeferedFn()
		commandData, err = cmdRoute(*flagStopsParams)
	} else if utilfuncs.StringInArray([]string{"dbload", "dbcompute", "dbstatus"}, os.Args[1]) {
		flagDbSet.Parse(os.Args[2:])
		commandData, err = cmdDb(os.Args[1], *flagDbParams)
	} else if os.Args[1] == "version" {
		commandData = "0.2.0"
	}

	//_ = format
	if err != nil {
		errorDie(err, false)
	} else if jsonBytes, err = json.MarshalIndent(commandData, "", "  "); err != nil {
		errorDie(err, false)
	} else {
		fmt.Println(string(jsonBytes))
	}
}

func cmdRoute(paramsYAML string) (any, error) {
	var routeParams apiroute.RouteParams
	if err := yaml.Unmarshal([]byte(paramsYAML), &routeParams); err != nil {
		return nil, fmt.Errorf("Error demarshalling YML %s: %v", paramsYAML, err)
	} else {
		return apiroute.CommandRoute(&routeParams)
	}
}

func cmdStops(paramsYAML string) (any, error) {
	var stopParams apistops.StopsParams
	if err := yaml.Unmarshal([]byte(paramsYAML), &stopParams); err != nil {
		return nil, fmt.Errorf("Error demarshalling YML %s: %v", paramsYAML, err)
	} else {
		return apistops.CommandStops(&stopParams)
	}
}

func cmdDb(command, paramsYAML string) (any, error) {
	var dbParams apidb.SourcesetFilter
	if err := yaml.Unmarshal([]byte(paramsYAML), &dbParams); err != nil {
		return nil, fmt.Errorf("Error demarshalling YML %s: %v", paramsYAML, err)
	} else {
		mobsqlConfig := apidb.Config{
			SQLiteDBPath: nil, LogInfo: true, LogWarn: true, LogDebug: true,
		}
		if command == "dbload" {
			return apidb.Load(dbParams, &mobsqlConfig)
		} else if command == "dbcompute" {
			return apidb.Compute(dbParams, &mobsqlConfig)
		} else if command == "dbstatus" {
			return apidb.Status(dbParams, &mobsqlConfig)
		}
	}
	return nil, nil
}

func errorDie(err error, showUsage bool) {
	if showUsage {
		flag.Usage()
	}
	if err != nil {
		logger.Fatal(err)
	}
	os.Exit(1)
}

func globalsSetup(debugCategories, debugProfile string) func() {
	// Setup debug categories
	if strings.Contains(debugCategories, "i") {
		logger.LogInfo = true
	}
	if strings.Contains(debugCategories, "w") {
		logger.LogWarn = true
	}
	if strings.Contains(debugCategories, "d") {
		logger.LogDebug = true
	}

	// Setup pprof debugging
	if debugProfile != "" {
		if f, err := os.Create(debugProfile); err != nil {
			log.Fatal(err)
		} else {
			pprof.StartCPUProfile(f)
			return pprof.StopCPUProfile
		}
	}

	return func() {}
}
